package com.stars.easyms.base.util;

import com.stars.easyms.base.bean.LazyLoadBean;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 反射类工具类
 *
 * @author guoguifang
 * @date 2018-10-19 10:07
 * @since 1.0.0
 */
@SuppressWarnings("unchecked")
public final class ReflectUtil {

    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private static final LazyLoadBean<ResourcePatternResolver> RESOURCE_PATTERN_RESOLVER = new LazyLoadBean<>(
            () -> ResourcePatternUtils.getResourcePatternResolver(ApplicationContextHolder.getApplicationContext()));

    private static final LazyLoadBean<MetadataReaderFactory> METADATA_READER_FACTORY = new LazyLoadBean<>(
            () -> new CachingMetadataReaderFactory(ApplicationContextHolder.getApplicationContext()));

    private static final Map<Class<?>, Map<Class<?>[], Constructor<?>>> CONSTRUCTOR_CACHE = new ConcurrentReferenceHashMap<>(32);

    private static final Map<Method, String> METHOD_FULL_NAME_CACHE = new ConcurrentReferenceHashMap<>(32);

    public static Class<?> forClassName(String className) {
        return null;
    }

    /**
     * 获取反射实例对象：如果参数都不为空的情况下使用该方法
     *
     * @param targetClass 目标class
     * @param parameters  参数
     * @return
     * @throws Exception 实例化异常
     */
    public static <T> T getInstance(Class<T> targetClass, Object... parameters) throws Exception {
        Class<?>[] parameterTypes = Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new);
        return getInstance(targetClass, parameterTypes, parameters);
    }

    /**
     * 获取反射实例对象：如果参数有为空的情况下使用该方法
     *
     * @param targetClass 目标class
     * @param parameters  参数
     * @return
     * @throws Exception 实例化异常
     */
    public static <T> T getInstance(Class<T> targetClass, Class<?>[] parameterTypes, Object[] parameters) throws Exception {
        Map<Class<?>[], Constructor<?>> constructorMap = CONSTRUCTOR_CACHE.computeIfAbsent(targetClass, t -> new ConcurrentHashMap<>(4));
        Constructor constructor = constructorMap.computeIfAbsent(parameterTypes, h -> ReflectUtil.getConstructor(targetClass, parameterTypes));
        if (constructor == null) {
            throw new NoSuchMethodException(targetClass.getName());
        }
        return (T) constructor.newInstance(parameters);
    }

    public static <T> Constructor<T> getConstructor(Class<T> targetClass, Class<?>[] parameterTypes) {
        try {
            return ReflectionUtils.accessibleConstructor(targetClass, parameterTypes);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    public static <T> T readStaticField(Class<?> cls, String fieldName) throws IllegalAccessException {
        return (T) FieldUtils.readStaticField(cls, fieldName, true);
    }

    public static <T> T readField(Object target, String fieldName) throws IllegalAccessException {
        return (T) FieldUtils.readField(target, fieldName, true);
    }

    @Nullable
    public static <T> T readStaticFinalField(Class<?> cls, String fieldName, boolean forceAccess) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(cls, fieldName, true);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        if (forceAccess) {
            forceAccessFinalField(field);
        }
        return (T) field.get(cls);
    }

    public static void writeStaticFinalField(Class<?> cls, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(cls, fieldName, true);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        forceAccessFinalField(field);
        field.set(cls, value);
    }

    public static <T> T readFinalField(Object target, String fieldName, boolean forceAccess) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(target.getClass(), fieldName, true);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        if (forceAccess) {
            forceAccessFinalField(field);
        }
        return (T) field.get(target);
    }

    public static <T> T readField(Object target, String fieldName, boolean forceAccess) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(target.getClass(), fieldName, forceAccess);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        return (T) field.get(target);
    }

    public static <T> T readFieldWithoutException(Object target, String fieldName) {
        Field field = FieldUtils.getField(target.getClass(), fieldName, true);
        if (field != null) {
            try {
                return (T) field.get(target);
            } catch (IllegalAccessException e) {
                // ignore
            }
        }
        return null;
    }

    public static void writeFinalField(Object target, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(target.getClass(), fieldName, true);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        forceAccessFinalField(field);
        field.set(target, value);
    }

    public static void writeField(Object target, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
        Field field = FieldUtils.getField(target.getClass(), fieldName, true);
        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }
        field.set(target, value);
    }

    /**
     * 获取所有加了参数注解的类集合，没有指定包的话默认找所有已加载过的类，有部分未加载类无法被获取到（慎用）
     *
     * @param annotationClasses 注解数组
     * @return 类集合
     */
    @NonNull
    public static Set<Class<?>> getAllClassByAnnotation(Class<?>... annotationClasses) {
        return getAllClassByAnnotation(Collections.emptySet(), annotationClasses);
    }

    /**
     * 获取所有加了参数注解的类集合
     *
     * @param basePackages      扫描包
     * @param annotationClasses 注解数组
     * @return 类集合
     */
    @NonNull
    public static Set<Class<?>> getAllClassByAnnotation(String basePackages, Class<?>... annotationClasses) {
        return getAllClassByAnnotation(asSet(basePackages), annotationClasses);
    }

    /**
     * 获取所有加了参数注解的类集合
     *
     * @param basePackageCollection 扫描包集合，如果为null则返回空集合，如果为空集合则默认扫描已加载的所有类
     * @param annotationClasses     注解数组
     * @return 类集合
     */
    @NonNull
    public static Set<Class<?>> getAllClassByAnnotation(@Nullable Collection<String> basePackageCollection, Class<?>... annotationClasses) {
        Set<Class<?>> allClassByAnnotation = new HashSet<>();
        if (basePackageCollection == null || annotationClasses.length == 0) {
            return allClassByAnnotation;
        }
        List<Class<?>> annotationClassList = Arrays.asList(annotationClasses);
        for (Class<?> clazz : getAllClasses(basePackageCollection)) {
            if (basePackageCollection.isEmpty() || startWith(clazz, basePackageCollection)) {
                Annotation[] annotations;
                try {
                    annotations = clazz.getDeclaredAnnotations();
                } catch (Throwable t) {
                    continue;
                }
                for (Annotation annotation : annotations) {
                    if (annotationClassList.contains(annotation.annotationType())) {
                        allClassByAnnotation.add(clazz);
                        break;
                    }
                }
            }
        }
        return allClassByAnnotation;
    }

    /**
     * 获取所有加了参数注解的类集合
     *
     * @param basePackageCollection 扫描包集合，如果为null则默认扫描已加载的所有类
     * @param annotationClasses     注解数组
     * @return 类集合
     */
    @NonNull
    public static Set<Class<?>> getAllClassByAnnotationWithDefaultAll(@Nullable Collection<String> basePackageCollection, Class<?>... annotationClasses) {
        return getAllClassByAnnotation(basePackageCollection == null ? Collections.emptySet() : basePackageCollection, annotationClasses);
    }

    /**
     * 获取所有加了参数注解的方法集合
     *
     * @param annotationClasses 注解数组
     * @return 方法集合
     */
    @NonNull
    public static Set<Method> getAllMethodByAnnotation(Class<?>... annotationClasses) {
        return getAllMethodByAnnotation(Collections.emptySet(), annotationClasses);
    }

    /**
     * 获取所有加了参数注解的方法集合
     *
     * @param basePackages      扫描包
     * @param annotationClasses 注解数组
     * @return 方法集合
     */
    @NonNull
    public static Set<Method> getAllMethodByAnnotation(String basePackages, Class<?>... annotationClasses) {
        return getAllMethodByAnnotation(asSet(basePackages), annotationClasses);
    }

    /**
     * 获取所有加了参数注解的方法集合
     *
     * @param basePackageCollection 扫描包集合，如果为null则返回空集合，如果为空集合则默认扫描已加载的所有类
     * @param annotationClasses     注解数组
     * @return 方法集合
     */
    @NonNull
    public static Set<Method> getAllMethodByAnnotation(@Nullable Collection<String> basePackageCollection, Class<?>... annotationClasses) {
        Set<Method> allMethodByAnnotation = new HashSet<>();
        if (basePackageCollection == null || annotationClasses.length == 0) {
            return allMethodByAnnotation;
        }
        List<Class<?>> annotationClassList = Arrays.asList(annotationClasses);
        for (Class<?> clazz : getAllClasses(basePackageCollection)) {
            if (basePackageCollection.isEmpty() || startWith(clazz, basePackageCollection)) {
                Method[] methods;
                try {
                    methods = clazz.getDeclaredMethods();
                } catch (Throwable t) {
                    continue;
                }
                for (Method method : methods) {
                    Annotation[] annotations = method.getDeclaredAnnotations();
                    for (Annotation annotation : annotations) {
                        if (annotationClassList.contains(annotation.annotationType())) {
                            allMethodByAnnotation.add(method);
                            break;
                        }
                    }
                }
            }
        }
        return allMethodByAnnotation;
    }

    /**
     * 获取所有加了参数注解的方法集合
     *
     * @param basePackageCollection 扫描包集合，如果为null则默认扫描已加载的所有类
     * @param annotationClasses     注解数组
     * @return 方法集合
     */
    @NonNull
    public static Set<Method> getAllMethodByAnnotationWithDefaultAll(@Nullable Collection<String> basePackageCollection, Class<?>... annotationClasses) {
        return getAllMethodByAnnotation(basePackageCollection == null ? Collections.emptySet() : basePackageCollection, annotationClasses);
    }

    public static String getMethodFullName(final Method method) {
        return METHOD_FULL_NAME_CACHE.computeIfAbsent(method, m -> {
            StringBuilder sb = new StringBuilder();
            sb.append(method.getDeclaringClass().getName()).append(".").append(method.getName()).append("(");
            Class<?>[] parameterTypes = method.getParameterTypes();
            int index = 0;
            for (Class<?> parameterType : parameterTypes) {
                if (index++ != 0) {
                    sb.append(", ");
                }
                String parameterTypeName = parameterType.getName();
                if (parameterTypeName.contains("[Z")) {
                    sb.append("boolean").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[Z") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[B")) {
                    sb.append("byte").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[B") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[C")) {
                    sb.append("char").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[C") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[D")) {
                    sb.append("double").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[D") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[F")) {
                    sb.append("float").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[F") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[I")) {
                    sb.append("int").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[I") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[J")) {
                    sb.append("long").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[J") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[S")) {
                    sb.append("short").append(parameterTypeName.substring(0, parameterTypeName.indexOf("[S") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.contains("[L") && parameterTypeName.contains(";")) {
                    sb.append(parameterTypeName, parameterTypeName.indexOf("[L") + 2, parameterTypeName.indexOf(';'))
                            .append(parameterTypeName.substring(0, parameterTypeName.indexOf("[L") + 1).replace("\\[", "[]"));
                } else if (parameterTypeName.startsWith("java.lang.")) {
                    sb.append(parameterTypeName.substring(10));
                } else {
                    sb.append(parameterTypeName);
                }
            }
            return sb.append(")").toString();
        });
    }

    @NonNull
    private static Set<Class<?>> getAllClasses(@Nullable Collection<String> basePackageCollection) {
        Set<Class<?>> allClassSet = new HashSet<>();
        if (basePackageCollection == null || basePackageCollection.isEmpty()) {
            Field field;
            try {
                field = ClassLoader.class.getDeclaredField("classes");
                field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("无法获取到当前线程的类加载器的classes域!");
            }
            // 如果配置了spring-boot-devtools的Jar包，classLoader会变成RestartClassLoader，因此需要整合起来
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            do {
                try {
                    // 由于该field的值是一个动态变化的值，先取出直接转换成array固化，然后再合并到set里
                    allClassSet.addAll(Arrays.asList(((Vector<Class<?>>) field.get(classLoader)).toArray(new Class<?>[0])));
                } catch (Exception e) {
                    // ignore
                }
            } while ((classLoader = classLoader.getParent()) != null);
        } else {
            try {
                for (String basePackage : basePackageCollection) {
                    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
                    Resource[] resources = RESOURCE_PATTERN_RESOLVER.getNonNullBean().getResources(packageSearchPath);
                    for (Resource resource : resources) {
                        if (resource.isReadable()) {
                            MetadataReader metadataReader = METADATA_READER_FACTORY.getNonNullBean().getMetadataReader(resource);
                            Class<?> clazz = ClassUtil.forName(metadataReader.getClassMetadata().getClassName());
                            if (clazz != null) {
                                allClassSet.add(clazz);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                throw new IllegalStateException("I/O failure during classpath scanning", e);
            }
        }
        return allClassSet;
    }

    private static String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(
                ApplicationContextHolder.getApplicationContext().getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    private static boolean startWith(Class<?> clazz, @NonNull Collection<String> basePackageCollection) {
        boolean flag = false;
        for (String basePackage : basePackageCollection) {
            if (clazz.getName().startsWith(basePackage)) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    @Nullable
    private static Set<String> asSet(String strs) {
        Set<String> set = null;
        if (StringUtils.isNotBlank(strs)) {
            String[] array = strs.split(",");
            for (String str : array) {
                if (StringUtils.isNotBlank(str)) {
                    // null和空集合代表意义不同
                    if (set == null) {
                        set = new HashSet<>();
                    }
                    set.add(str);
                }
            }
        }
        return set;
    }

    private static void forceAccessFinalField(Field field) throws IllegalAccessException {
        if (Modifier.isFinal(field.getModifiers())) {
            Field modifiersField = FieldUtils.getField(Field.class, "modifiers", true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        }
    }

    public static Map<Class<? extends Annotation>, Annotation> getDeclaredAnnotationsInField(Field field) throws InvocationTargetException {
        try {
            final Method declaredAnnotationsMethod = Field.class.getDeclaredMethod("declaredAnnotations");
            final boolean doForceAccess = !declaredAnnotationsMethod.isAccessible();
            if (doForceAccess) {
                declaredAnnotationsMethod.setAccessible(true);
            }
            try {
                return (Map<Class<? extends Annotation>, Annotation>) declaredAnnotationsMethod.invoke(field);
            } finally {
                if (doForceAccess) {
                    declaredAnnotationsMethod.setAccessible(false);
                }
            }
        } catch (final NoSuchMethodException | IllegalAccessException ignored) {
            // contains always a privateGetDeclaredFields method , The privateGetDeclaredFields method is made accessible
        }
        return null;
    }

    public static Field[] getOriginalDeclaredFieldsInClass(Class<?> clazz) throws InvocationTargetException {
        try {
            final Method privateGetDeclaredFieldsMethod = Class.class.getDeclaredMethod("privateGetDeclaredFields", boolean.class);
            final boolean doForceAccess = !privateGetDeclaredFieldsMethod.isAccessible();
            if (doForceAccess) {
                privateGetDeclaredFieldsMethod.setAccessible(true);
            }
            try {
                return (Field[]) privateGetDeclaredFieldsMethod.invoke(clazz, false);
            } finally {
                if (doForceAccess) {
                    privateGetDeclaredFieldsMethod.setAccessible(false);
                }
            }
        } catch (final NoSuchMethodException | IllegalAccessException ignored) {
            // contains always a privateGetDeclaredFields method , The privateGetDeclaredFields method is made accessible
        }
        return new Field[0];
    }

    public static void changeFieldModifiers(Field field, int modifiers) {
        try {
            final Field modifiersField = Field.class.getDeclaredField("modifiers");
            final boolean doForceAccess = !modifiersField.isAccessible();
            if (doForceAccess) {
                modifiersField.setAccessible(true);
            }
            try {
                modifiersField.setInt(field, modifiers);
            } finally {
                if (doForceAccess) {
                    modifiersField.setAccessible(false);
                }
            }
        } catch (final NoSuchFieldException | IllegalAccessException ignored) {
            // The field class contains always a modifiers field , The modifiers field is made accessible
        }
    }

    public static void changeFieldDeclaredAnnotations(Field field, Map<Class<? extends Annotation>, Annotation> declaredAnnotations) {
        try {
            final Field declaredAnnotationsField = Field.class.getDeclaredField("declaredAnnotations");
            final boolean doForceAccess = !declaredAnnotationsField.isAccessible();
            if (doForceAccess) {
                declaredAnnotationsField.setAccessible(true);
            }
            try {
                declaredAnnotationsField.set(field, declaredAnnotations);
            } finally {
                if (doForceAccess) {
                    declaredAnnotationsField.setAccessible(false);
                }
            }
        } catch (final NoSuchFieldException | IllegalAccessException ignored) {
            // The field class contains always a modifiers field , The modifiers field is made accessible
        }
    }

    /**
     * 为了兼容低于commons-lang3低于3.5的版本，该方法直接copy的commons-lang3的3.5以上版本
     * <p>Retrieves a method whether or not it's accessible. If no such method
     * can be found, return {@code null}.</p>
     *
     * @param cls            The class that will be subjected to the method search
     * @param methodName     The method that we wish to call
     * @param parameterTypes Argument class types
     * @return The method
     */
    public static Method getMatchingMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) {
        Validate.notNull(cls, "Null class not allowed.");
        Validate.notEmpty(methodName, "Null or blank methodName not allowed.");

        // Address methods in superclasses
        Method[] methodArray = cls.getDeclaredMethods();
        final List<Class<?>> superclassList = org.apache.commons.lang3.ClassUtils.getAllSuperclasses(cls);
        for (final Class<?> klass : superclassList) {
            methodArray = ArrayUtils.addAll(methodArray, klass.getDeclaredMethods());
        }

        Method inexactMatch = null;
        for (final Method method : methodArray) {
            if (methodName.equals(method.getName()) &&
                    Objects.deepEquals(parameterTypes, method.getParameterTypes())) {
                return method;
            } else if (methodName.equals(method.getName()) &&
                    org.apache.commons.lang3.ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true) &&
                    (inexactMatch == null ||
                            distance(parameterTypes, method.getParameterTypes()) < distance(parameterTypes, inexactMatch.getParameterTypes()))) {
                inexactMatch = method;
            }

        }
        return inexactMatch;
    }

    /**
     * 为了兼容低于commons-lang3低于3.5的版本，该方法直接copy的commons-lang3的3.5以上版本
     * <p>Returns the aggregate number of inheritance hops between assignable argument class types.  Returns -1
     * if the arguments aren't assignable.  Fills a specific purpose for getMatchingMethod and is not generalized.</p>
     *
     * @param classArray   classArray
     * @param toClassArray toClassArray
     * @return the aggregate number of inheritance hops between assignable argument class types.
     */
    private static int distance(final Class<?>[] classArray, final Class<?>[] toClassArray) {
        int answer = 0;

        if (!org.apache.commons.lang3.ClassUtils.isAssignable(classArray, toClassArray, true)) {
            return -1;
        }
        for (int offset = 0; offset < classArray.length; offset++) {
            // Note InheritanceUtils.distance() uses different scoring system.
            if (!classArray[offset].equals(toClassArray[offset])) {
                if (org.apache.commons.lang3.ClassUtils.isAssignable(classArray[offset], toClassArray[offset], true)
                        && !org.apache.commons.lang3.ClassUtils.isAssignable(classArray[offset], toClassArray[offset], false)) {
                    answer++;
                } else {
                    answer = answer + 2;
                }
            }
        }

        return answer;
    }

    private ReflectUtil() {
    }
}
